import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;


/**
 * Computer Network, Programming Assignment #2
 * 
 * PG Peer Class
 * @author Yancheng Li
 *
 */
public class PGPeer {

	private static final int basePort = 7000;
	private static final int gridWidth = 3;
	private static final int gridLength = 3;	

	
	private Point pos;
	private int inPort;
	private int outPort;
	private int routingPort;
	private int GRIPInPort;
	private int GRIPOutPort;
	private PeerServer peerServer;
	private SendServer sendServer;
	private ReceiveServer receiveServer;
	private GRIPReceiveServer gripReceiveServer;
	private GRIPSendServer gripSendServer;
	private HashMap<String, Socket> peerSockeList;
	private HashMap<String, DataOutputStream> peerOutList;
	private HashMap<String, Socket> listenSocketList;//listening port
	private HashMap<String, DataOutputStream> listenOutList;
	private static HashMap<Point, Integer> GRIPTable;
	private boolean print;
	private Random rd = new Random(1024);
	
	public PGPeer(int x, int y){
		
		pos = new Point(x, y);
		inPort = basePort + ((x * gridWidth) + y) * 5;
		outPort = inPort + 1;
		routingPort = outPort + 1;
		GRIPOutPort = routingPort + 1;
		GRIPInPort = GRIPOutPort + 1;
		peerServer = new PeerServer(pos, this, routingPort);
		sendServer = new SendServer(pos, this, inPort);
		receiveServer = new ReceiveServer(pos, this, outPort);
		gripReceiveServer = new GRIPReceiveServer(pos, this, GRIPInPort); // These were flipped 
		gripSendServer = new GRIPSendServer(pos, this,GRIPOutPort);
		peerSockeList = new HashMap<String, Socket>();
		listenSocketList = new HashMap<String, Socket>();
		peerOutList = new HashMap<String, DataOutputStream>();
		listenOutList = new HashMap<String, DataOutputStream>();
		GRIPTable = new HashMap<Point, Integer>();
		this.print = true;
		
		//getLinkLengthTable(pos, "LinkLength.txt"); // Fix

	}
	
	//turn on echo
	
	public void echoOn(){
		PSPUtil.print("Echo mode on", true);
		this.print = true;
	}
	
	//turn off echo
	
	public void echoOff(){
		PSPUtil.print("Echo mode off", true);
		this.print = false;
	}
	
	public boolean getPrint(){
		return print;
	}
	
	public HashMap getGRIPTable(){
		return this.GRIPTable;
	}
	
	public void setGRIPTable(Point pos, Integer distance){
		//System.out.println(pos + " = " + GRIPTable.containsKey(pos));
		GRIPTable.put(new Point(pos.getX(), pos.getY()), distance);
		
		return;
	}
	
	public void showGRIPTable(String title){
		System.out.println("\n"+title);
		
		Iterator it = GRIPTable.entrySet().iterator();
		
		while(it.hasNext()){
			Map.Entry<Point, Integer> entry = (Map.Entry<Point, Integer>) it.next();
			System.out.println(this.pos + " -> " + entry.getKey().toString() + " = " + entry.getValue());
		}
	}
	
	// Checks to see if that particular node is present in the table
	public boolean checkGRIPTable(Point f){
		if (GRIPTable.containsKey(f))
			return true;
		else
			return false;
	}
	
	//start the server
	
	public void startServer(){
		
		peerServer.start();
		sendServer.start();
		receiveServer.start();
		gripReceiveServer.start();
	}
	
	// Start the GRIP servers
	public void startGRIP(){
		getLinkLengthTable(pos, "LinkLength.txt"); // Fix
		gripSendServer.start();
	}
	
	//shutdown
	
	public void shutdown(){
		peerServer.shutdown();
		try{
			
			Collection<DataOutputStream> outCollection = peerOutList.values();
			Iterator<DataOutputStream> itOut = outCollection.iterator();
			while(itOut.hasNext()){
				itOut.next().close();
			}
			
			outCollection = listenOutList.values();
			itOut = outCollection.iterator();
			while(itOut.hasNext()){
				itOut.next().close();
			}
			
			Collection<Socket> socketCollection = peerSockeList.values();
			Iterator<Socket> itSocket = socketCollection.iterator(); 
			while(itSocket.hasNext()){
				System.out.println("Peer port closed: " + itSocket.next().getPort());
				itSocket.next().close();
			}
			
			socketCollection = listenSocketList.values();
			itSocket = socketCollection.iterator(); 
			while(itSocket.hasNext()){
				System.out.println("Listen port closed: " + itSocket.next().getPort());
				itSocket.next().close();
			}
			
		} catch (UnknownHostException e) {		
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	// add a listen socket
	
	public void addListenSocket(int port, Socket socket){
		
		try {
			listenSocketList.put(port + "", socket);
			DataOutputStream out = 				
				new DataOutputStream(socket.getOutputStream());
			listenOutList.put(port + "", out);
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		return;
	}
	
	// connect to neighbors peers
	
	public void connectPeers(){
		try {
			if(pos.getX() > 0){
				Point peerPoint = new Point(pos.getX() - 1, pos.getY());
				int peerPort = calRoutingPort(peerPoint);
								
				Socket peerSocket = new Socket("localhost", peerPort);
				DataOutputStream out = 				
					new DataOutputStream(peerSocket.getOutputStream());
				
				peerSockeList.put(peerPoint.toString(), peerSocket);
				peerOutList.put(peerPoint.toString(), out);
				
				System.out.println("Add peer" + peerPoint);
			}
			
			if(pos.getX() + 1 < gridLength){
				Point peerPoint = new Point(pos.getX() + 1, pos.getY());
				int peerPort = calRoutingPort(peerPoint);
				
				Socket peerSocket = new Socket("localhost", peerPort);
				DataOutputStream out = 				
					new DataOutputStream(peerSocket.getOutputStream());
				
				peerSockeList.put(peerPoint.toString(), peerSocket);
				peerOutList.put(peerPoint.toString(), out);
				
				System.out.println("Add peer" + peerPoint);
			}
			
			if(pos.getY() > 0){
				Point peerPoint = new Point(pos.getX(), pos.getY() - 1);
				int peerPort = calRoutingPort(peerPoint);
				
				Socket peerSocket = new Socket("localhost", peerPort);
				DataOutputStream out = 				
					new DataOutputStream(peerSocket.getOutputStream());
				
				peerSockeList.put(peerPoint.toString(), peerSocket);
				peerOutList.put(peerPoint.toString(), out);
				
				System.out.println("Add peer" + peerPoint);
			}
			
			if(pos.getY() + 1 < gridWidth){
				Point peerPoint = new Point(pos.getX(), pos.getY() + 1);
				int peerPort = calRoutingPort(peerPoint);
				
				Socket peerSocket = new Socket("localhost", peerPort);
				DataOutputStream out = 				
					new DataOutputStream(peerSocket.getOutputStream());
				
				peerSockeList.put(peerPoint.toString(), peerSocket);
				peerOutList.put(peerPoint.toString(), out);
				
				PSPUtil.print(("Add peer" + peerPoint), this.getPrint());
			}
			
			
			
		
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static int calRoutingPort(Point pos){
		int peerPort = basePort + ((pos.getX() * gridWidth) + pos.getY()) * 5 + 2; 
		PSPUtil.print("Connecting to peer port " + peerPort, true);
		return peerPort;
	}
	
	public static int calInPort(Point pos){
		int peerPort = basePort + ((pos.getX() * gridWidth) + pos.getY()) * 5; 		
		return peerPort;
	}
	
	public static int calOutPort(Point pos){
		int peerPort = basePort + ((pos.getX() * gridWidth) + pos.getY()) * 5 + 1; 		
		return peerPort;
	}
	
	public static int calGRIPOutPort(Point pos){
		int peerPort = basePort + ((pos.getX() * gridWidth) + pos.getY()) * 5 + 3;
		return peerPort;
	}
	
	public static int calGRIPInPort(Point pos){
		int peerPort = basePort + ((pos.getX() * gridWidth) + pos.getY()) * 5 + 4;
		return peerPort;
	}
	
	public void sendPacketTable(GPDatagram packet){
		Point dst = new Point(packet.getX(), packet.getY());
		
		// Get the output stream
		DataOutputStream out = null; //listenOutList.get(packet.getPort() + "");
		if(out == null){
			try {
				out = new DataOutputStream(new Socket("localhost", packet.getPort()).getOutputStream());
				//listenOutList.put(packet.getPort() + "", out);
			} catch (Exception e) {
				e.printStackTrace();
			}		
		}
		PSPUtil.print("Sending updated table to " + dst + "\n" + packet, getPrint());
		
		try {
			char[] temp = packet.getRawdata();
			byte[] byteData = PSPUtil.convert2Byte(packet.getRawdata());
			out.write(byteData);			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
	}
	
	// forward the packet
	public void sendPacket(GPDatagram packet){
		
		Point dst = new Point(packet.getX(), packet.getY());
		if(dst.getX() < pos.getX()){
			Point next = new Point(pos.getX() - 1, pos.getY());
			sendToPeer(next, packet);
			return;
		}
		
		if(dst.getX() > pos.getX()){
			Point next = new Point(pos.getX() + 1, pos.getY());
			sendToPeer(next, packet);
			return;
		}
		
		if(dst.getY() < pos.getY()){
			Point next = new Point(pos.getX(), pos.getY() - 1);
			sendToPeer(next, packet);
			return;
		}
		
		if(dst.getY() > pos.getY()){
			Point next = new Point(pos.getX(), pos.getY() + 1);
			sendToPeer(next, packet);
			return;
		}
		
		PSPUtil.print("Packet reach destination: " + packet, this.getPrint());
		sendToDst(packet);
				
	}
	
	// send to listening connection
	public void sendToDst(GPDatagram packet){
		DataOutputStream out = listenOutList.get(packet.getPort() + "");
		if(out == null)
			return;
		
		try {
			PSPUtil.print("Packet forwad to listening socket: " + packet, this.getPrint());
			byte[] byteData = PSPUtil.convert2Byte(packet.getRawdata());
			out.write(byteData);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	// forward to peers
	public void sendToPeer(Point pos, GPDatagram packet){
		
		DataOutputStream out = peerOutList.get(pos.toString());
		if(out == null)
			return;
		PSPUtil.print("Forward to peer " + pos + "\n" + packet, this.getPrint());
				
		try {
			byte[] byteData = PSPUtil.convert2Byte(packet.getRawdata());
			out.write(byteData);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	// Access the current LinkLengthTable for this position  
	public static void getLinkLengthTable(Point pos, String filename){
		try{
			BufferedReader in = new BufferedReader(new FileReader(filename));
			String str;
						
			while((str = in.readLine()) != null){
								
				// Ignore comments in file
				if (str.indexOf("//") >= 0)
					continue;
				
				String[] strArr = str.split(" ");
				if (strArr.length < 5){
					System.out.println("Empty");
					continue;
				}
					
				
				if (pos.equals(new Point(Integer.parseInt(strArr[0]), Integer.parseInt(strArr[1])))){				
				// Add the destination point to my GRIP table
				Point destPoint = new Point(Integer.parseInt(strArr[2]), Integer.parseInt(strArr[3]));
				GRIPTable.put(destPoint, Integer.parseInt(strArr[4]));										
				}
			}
			
			Iterator it = GRIPTable.entrySet().iterator();
			
			while(it.hasNext()){
				Map.Entry<Point, Integer> entry = (Map.Entry<Point, Integer>) it.next();
				//System.out.println(entry.getKey().toString() + " -> " + entry.getValue());
			}
		}
		catch (Exception e){
			e.printStackTrace();
		}
		
		return;
	}

	
}
